/*
 * Created on Feb 8, 2008
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package it.unibo.deis.lia.mmhc.rom;

import it.unibo.deis.lia.mmhc.util.*;
import it.unibo.deis.lia.networkInterface.*;

import java.net.*;
import java.io.*;
import java.util.*;

public class ServerRoutingManager extends Thread {

	private static final String LEASEFILE_PATH = "/var/lib/dhcp3/";
	// private static final String LEASEFILE_PATH = "/var/db/";//mio db dhcpd

	// private static final boolean ENABLE_NAT=false;

	// private Path currentPath=null;
	// private double remoteClientRequiredReliability;
	private RoutingManager rm;

	// XXX AAA di tanto in tanto ripulire currentClients dai client vecchi e
	// terminare i rispettivi CheckPathAvailability
	private Hashtable<String, CheckPathAvailability> currentClients = new Hashtable<String, CheckPathAvailability>();

	private GenericNetworkInterface interf;

	private String localIP;

	protected ServerRoutingManager(GenericNetworkInterface interf,
			RoutingManager rm) {
		this(interf, null, rm);
	}

	protected ServerRoutingManager(GenericNetworkInterface interf,
			String address, RoutingManager rm) {
		this.interf = interf;
		localIP = address;
		this.rm = rm;
	}

	public void run() {
		System.out
				.println("\tServerRoutingManager new interface providing connectivity: "
						+ interf);
		// CheckPathAvailability checkPathAvailability=null;
		try {
			// double remoteClientRequiredReliability=1.0;

			// * informazioni che devo recuperare
			// * a) quanti client sto servendo?
			// * b) quale throughput ho a disposizione?
			// * c) quanti hop?

			// * a) il client specifica RequiredReliability
			// * b) io gli rispondo con le info sul path che mi pare migliore
			// per quel client
			// * c) di conseguenza aggiorno le tabelle di routing (throughput,
			// hops, clients, path mobility)

			// XXX AAA testare ServerSocket ss=new ServerSocket(2400,0,null);
			// double localThroughput;
			if (interf instanceof IEEE80211) {
				IEEE80211 ieee80211 = (IEEE80211) interf;
				localIP = ieee80211.getConnectorRoleIP();
				// localThroughput=ieee80211.
			}
			if (interf instanceof Bluetooth) {
				Bluetooth blue = (Bluetooth) interf;
				localIP = blue.getConnectorRoleIP();
				// localThroughput=ieee80211.
			}

			ServerSocket ss = new ServerSocket(2400, 0, InetAddress
					.getByName(localIP));
			while (true) {

				System.out.println("\tServerRoutingManager accept on "
						+ localIP);
				Socket s = ss.accept();

				String remoteClientIP = s.getInetAddress().getHostAddress();
				System.out.println("\tServerRoutingManager remoteClientIP "
						+ remoteClientIP);
				// String localClientIP=s.getLocalAddress().getHostAddress();
				System.out.println("\tServerRoutingManager a client "
						+ remoteClientIP + " " + System.currentTimeMillis());

				ObjectOutputStream oos = new ObjectOutputStream(s
						.getOutputStream());
				oos.flush();
				ObjectInputStream ois = new ObjectInputStream(s
						.getInputStream());
				double remoteClientRequiredReliability = ois.readDouble();
				System.out
						.println("\tServerRoutingManager received remoteClientRequiredReliability "
								+ remoteClientRequiredReliability
								+ " from "
								+ remoteClientIP);

				System.out
						.println("\tServerRoutingManager pre path selection for "
								+ remoteClientIP
								+ " "
								+ System.currentTimeMillis());
				Path bestPath = bestPath(remoteClientRequiredReliability,
						localIP);
				System.out
						.println("\tServerRoutingManager post path selection for "
								+ remoteClientIP
								+ " "
								+ System.currentTimeMillis());
				System.out.println("\tServerRoutingManager bestPath "
						+ bestPath + " for " + remoteClientIP);
				// currentPath=bestPath;

				CheckPathAvailability checkPathAvailability = currentClients
						.get(remoteClientIP);
				if (checkPathAvailability == null) {
					System.out
							.println("\tServerRoutingManager new client!!! "
									+ remoteClientIP + " "
									+ System.currentTimeMillis());
					checkPathAvailability = new CheckPathAvailability(localIP,
							remoteClientIP, remoteClientRequiredReliability,
							bestPath);
					checkPathAvailability.start();
					currentClients.put(remoteClientIP, checkPathAvailability);
				} else {
					checkPathAvailability.remoteClientRequiredReliability = remoteClientRequiredReliability;
					checkPathAvailability.currentPath = bestPath;
				}
				if (bestPath == null)
					bestPath = checkPathAvailability.currentPath;
				if (bestPath == null) {
					// System.err.println("Bestpath ancora nullo");
				} else
					reroute(bestPath, remoteClientIP);

				// float throughput;
				// System.out.println("\tServerRoutingManager currentPath "+currentPath);
				// System.out.println("\tServerRoutingManager currentPath.getChannel() "+currentPath.getChannel());
				// System.out.println("\tServerRoutingManager currentPath.getChannel() "+interf);
				// if(currentPath.getThroughputKBs() <
				// currentPath.getChannel().getConnector().getThroughputKBs()){
				// throughput=currentPath.getThroughputKBs();
				// }
				// else{
				// throughput=currentPath.getChannel().getConnector().getThroughputKBs();
				// }
				// int clients=
				// Path sentPath=new
				// Path(null,currentPath.getHops()+1,throughput,activeClients(localIP),currentPath.getPathMobility());

				if (bestPath != null) {
					Path sentPath = new Path(null, bestPath.getHops() + 1,
							bestPath.getThroughputKBs(),
							activeClients(localIP), bestPath.getPathMobility());
					oos.writeObject(sentPath);
					oos.flush();
					System.out.println("\tServerRoutingManager sentPath "
							+ sentPath + " to " + remoteClientIP);
					s.close();
					System.out.println("\tServerRoutingManager closed socket "
							+ remoteClientIP);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		/*
		 * if(remoteClientIP!=null){ CheckPathAvailability
		 * checkPathAvailability=currentClients.get(remoteClientIP);
		 * if(checkPathAvailability!=null){ checkPathAvailability.stopThread();
		 * } }
		 */
		System.out
				.println("\tServerRoutingManager finished interface providing connectivity: "
						+ interf);
	}

	private class CheckPathAvailability extends Thread {
		private String localIP;
		private String remoteClientIP;
		private boolean activeThread = true;
		protected double remoteClientRequiredReliability;
		protected Path currentPath;

		public CheckPathAvailability(String localIP, String remoteClientIP,
				double remoteClientRequiredReliability, Path currentPath) {
			this.localIP = localIP;
			this.remoteClientIP = remoteClientIP;
			this.remoteClientRequiredReliability = remoteClientRequiredReliability;
			this.currentPath = currentPath;
		}

		public void stopThread() {
			this.activeThread = false;
		}

		public void run() {
			System.out
					.println("\tServerRoutingManager.CheckPathAvailability: remoteClientIP "
							+ remoteClientIP + "   localIP " + localIP);
			try {
				// TODO AAA di tanto in tento controlla che il path sia ancora
				// disponibile;
				// cambia la regola di routing se il path in uso e' scomparso
				while (activeThread) {
					boolean available = false;
					do {
						sleep(2000);

						if (currentPath != null) {
							Vector<Channel> availableChannels = rm
									.getAvailableChannels();
							for (int i = 0; i < availableChannels.size()
									&& available == false; i++) {
								Channel aChannel = availableChannels
										.elementAt(i);
								// System.out.println("RoutingManager available channel: "+channel);
								if (aChannel.getChannelLocalIPAddress().equals(
										currentPath.getChannel()
												.getChannelLocalIPAddress())) {
									available = true;
								}
							}
						}
					} while ((currentPath == null || available == true)
							&& activeThread);

					System.out
							.println("\tServerRoutingManager.CheckPathAvailability LOST PATH!!!: remoteClientIP "
									+ remoteClientIP + "   localIP " + localIP);
					Path bestPath = bestPath(remoteClientRequiredReliability,
							localIP);
					currentPath = bestPath;
					if (bestPath == null)
						bestPath = currentPath;
					reroute(bestPath, remoteClientIP);

				}
			} catch (Exception e) {
				e.printStackTrace();
				System.err
						.println("é qui l'errore del routing manager java null pointer exception");
			}
			System.out
					.println("\tServerRoutingManager.CheckPathAvailability FINISHED: remoteClientIP "
							+ remoteClientIP + "   localIP " + localIP);

		}
	}

	// trovare i client attivi guardando la iptable data dal dhcp server
	private int activeClients(String localIP) throws Exception {
		int clients = 0;
		BufferedReader leases = null;
		if (System.getProperty("os.name").startsWith("Linux")) {
			leases = new BufferedReader(new FileReader(LEASEFILE_PATH
					+ "dhcpd.leases"));
		} else {
			throw new Exception("Not supported Operating System");
		}
		String line = leases.readLine();
		while (line != null) {
			if (!line.startsWith("#") && line.contains("lease")) {
				String tokens[] = line.split("[ {]");

				System.out.println("\t\tactiveClients remoteOctects "
						+ tokens[1]);
				String remoteOctects[] = tokens[1].split("[.]");

				System.out.println("\t\tactiveClients localIP " + localIP);
				String localOctects[] = localIP.split("[.]");

				if (remoteOctects[0].equals(localOctects[0])
						&& remoteOctects[1].equals(localOctects[1])
						&& remoteOctects[2].equals(localOctects[2])) {
					do {
						line = leases.readLine();
					} while (line != null && !line.contains("ends"));
					tokens = line.split("[ :;/]");
					int sec = Integer.parseInt(tokens[tokens.length - 1]);
					int min = Integer.parseInt(tokens[tokens.length - 2]);
					int hour = Integer.parseInt(tokens[tokens.length - 3]);
					int day = Integer.parseInt(tokens[tokens.length - 4]);
					int month = Integer.parseInt(tokens[tokens.length - 5]);
					int year = Integer.parseInt(tokens[tokens.length - 6]);
					System.out.println("\t\tactiveClients " + year + " "
							+ (month - 1) + " " + day + " " + hour + " " + min
							+ " " + sec);
					Calendar time = Calendar.getInstance();
					time.setTimeZone(TimeZone.getTimeZone("UTC"));
					time.set(year, (month - 1), day, hour, min, sec);
					if (time.getTimeInMillis() > System.currentTimeMillis()) {
						clients++;
						System.out.println("\t\tactiveClients clients++: "
								+ clients);
					}
				}
			}
			line = leases.readLine();
		}
		System.out.println("\t\tactiveClients finish clients: " + clients);
		return clients;
	}

	private Path bestPath(double requiredReliability, String localIP)
			throws Exception {
		// XXX AAA metodo di valutazione modificabile a runtime
		Vector<Path> candidatePaths = new Vector<Path>();
		Vector<Path> availablePaths = rm.getAvailablePaths();

		for (int j = 0; j < availablePaths.size(); j++) {
			Path aPath = availablePaths.elementAt(j);
			if (aPath.getPathMobility() >= requiredReliability * 0.8) {
				candidatePaths.addElement(aPath);
			}
		}

		if (candidatePaths.size() == 0) {
			for (int j = 0; j < availablePaths.size(); j++) {
				Path aPath = availablePaths.elementAt(j);
				if (aPath.getPathMobility() >= requiredReliability * 0.5) {
					candidatePaths.addElement(aPath);
				}
			}
		}

		if (candidatePaths.size() == 0) {
			for (int j = 0; j < availablePaths.size(); j++) {
				Path aPath = availablePaths.elementAt(j);
				if (aPath.getPathMobility() >= requiredReliability * 0.0) {
					candidatePaths.addElement(aPath);
				}
			}
		}

		Path bestPath = null;
		for (int i = 0; i < candidatePaths.size(); i++) {
			Path aPath = candidatePaths.elementAt(i);
			if (bestPath == null) {
				bestPath = aPath;
			} else {
				int clients = activeClients(localIP);

				float bestPathThroughput = bestPath.getThroughputKBs()
						/ clients;
				float aPathThroughput = aPath.getThroughputKBs() / clients;
				if (aPathThroughput > bestPathThroughput) {
					bestPath = aPath;
				}
			}
		}
		// if(bestPath==null)
		// bestPath=new Path(null,)
		return bestPath;
	}

	private void reroute(Path bestPath, String remoteClientIP) throws Exception {

		// TODO: togliere vecchie regole di routing quando non sono piu` valide
		// o non servono piu`
		String external = null;
		try {

			external = bestPath.getChannel().getConnector()
					.getLocalNetworkInterface().getDescription();

		} catch (NullPointerException e) {
			System.err
					.println("Non riesce a trovare la descrizione dell'interfaccia!!");

		}

		if (System.getProperty("os.name").startsWith("Linux")
				|| System.getProperty("os.name").startsWith("Mac")) {
			// aggiornare le tabelle di routing!!!
			//

			// pan per ogni hci interface

			if (external.startsWith("hci")) {
				external = "pan" + external.charAt(3);
			}

			// / * NB: nella versione attuale mi accorgo di un nuovo client non
			// col dhcp server come prima,
			// * ma poiche' il nuovo client mi contatta direttamente

			System.out
					.println("\tServerRoutingManager interf.getDescription() "
							+ interf.getDescription());
			System.out.println("\tServerRoutingManager external " + external);
			System.out.println("\tServerRoutingManager remoteClientIP "
					+ remoteClientIP);

			// TODO: inserire controllo per evitare di avere le stesse regole
			// ripetute n volte
			Process p = Runtime.getRuntime().exec(
					"iptables -A FORWARD -i " + interf.getDescription()
							+ " -o " + external + " -j ACCEPT");
			p.waitFor();
			p.destroy();
			p = Runtime.getRuntime().exec(
					"iptables -A FORWARD -o " + interf.getDescription()
							+ " -i " + external + " -j ACCEPT");
			p.waitFor();
			p.destroy();
			// p=Runtime.getRuntime().exec("iptables -A INPUT -i "+interf.getDescription()+" -j ACCEPT");
			// p.waitFor(); p.destroy();
			// p=Runtime.getRuntime().exec("iptables -A INPUT -i "+external+" -j ACCEPT");
			// p.waitFor(); p.destroy();
			// p=Runtime.getRuntime().exec("iptables -A OUTPUT -o "+interf.getDescription()+" -j ACCEPT");
			// p.waitFor(); p.destroy();
			// p=Runtime.getRuntime().exec("iptables -A OUTPUT -o "+external+" -j ACCEPT");
			// p.waitFor(); p.destroy();
			// if(ENABLE_NAT){
			String command = "iptables -t nat -A POSTROUTING -o " + external
					+ " -s " + remoteClientIP + " -j MASQUERADE";
			p = Runtime.getRuntime().exec(command);
			System.out.println("\tServerRoutingManager " + command);
			// }
			// else{
			// p=Runtime.getRuntime().exec("route add "+remoteClientIP+" dev "+external);
			// occorre aggiungere regole di routing lungo tutto il percorso..,
			// }
			p.waitFor();
			p.destroy();
			System.out.println("\tServerRoutingManager path ready for "
					+ remoteClientIP + " " + System.currentTimeMillis());
		} else if (System.getProperty("os.name").startsWith("Windows")) {
			// TODO AAA testare!!! Windows!!!
			// Process
			// p=Runtime.getRuntime().exec("iptables -A FORWARD -i "+interf.getDescription()+" -o "+external+" -j ACCEPT");
			Process p = Runtime.getRuntime().exec(
					"route add " + remoteClientIP + " IF "
							+ interf.getDescription());
			p.waitFor();
			p.destroy();
		} else {
			throw new Exception("Not supported Operating System");
		}

	}
}
